/************************************************************************
                                                                     
   Xceed Ultimate ListBox for Silverlight                                                                                                                                            
   Copyright (C) 2010 Xceed Software Inc.    
                                                                     
   This program is provided to you under the terms of the GNU General Public  
   License version 2 as published by the Free Software Foundation. 
        
   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY, without even the implied warranty of 
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License along 
   with this program, if not, write to the Free Software Foundation, Inc., 
   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

   This program can be provided to you by Xceed Software Inc. under a
   proprietary commercial license agreement for use in non-Open Source
   projects. Visit Xceed.com to find the commercial edition of 
   Xceed Ultimate ListBox for Silverlight.                                    
                                                                      
 **********************************************************************/

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using Xceed.Silverlight.Data.Stats;
using Xceed.Silverlight.Data.Utils;

namespace Xceed.Silverlight.Data
{
  internal class DataSourceProviderEnumerator<T> : AsyncEnumerator<T>, ISLWeakEventListener where T : RawDataItem
  {
    #region Static Fields

    private static readonly Guid JumpMoveAndFetchCallId = DataUtils.CreateCallId();
    private static readonly Guid JumpMoveAndFetchOneWayCallId = DataUtils.CreateCallId();
    private static readonly Guid NilCountCallId = DataUtils.CreateCallId();
    private static readonly Guid NilGroupCountCallId = DataUtils.CreateCallId();
    private static readonly Guid GetStatValueCallId = DataUtils.CreateCallId();

    #endregion

    #region Constructor

    internal DataSourceProviderEnumerator(
      DataSourceProvider provider,
      DataSourceProviderHelper dataHelper,
      FilteringHandler filterHandler )
    {
      if( provider == null )
        throw new ArgumentNullException( "provider" );

      m_provider = provider;
      m_dataHelper = dataHelper;
      m_filterHandler = filterHandler;

      DataSourceChangedEventManager.AddListener( provider, this );
    }

    #endregion

    #region IsFullySynchronized Property

    public override bool IsFullySynchronized
    {
      get
      {
        return !m_provider.IsAsynchronous;
      }
    }

    #endregion

    public override IAsyncResult BeginCount(
      CountParameters parameters,
      AsyncCallback callback,
      object state )
    {
      CountQueryParameters queryParameters;

      // The DataSourceProvider cannot handle the query.
      if( !this.CreateCountRequest( parameters.StartPosition, parameters.CountChildOnly, out queryParameters ) )
      {
        AsyncResult asyncResult = new AsyncResult( DataSourceProviderEnumerator<T>.NilCountCallId, callback, state );
        asyncResult.StartSync( null );

        return asyncResult;
      }
      else
      {
        AsyncCallback middleTierCallback = this.GetMiddleTierCallback( callback );

        return m_provider.BeginExecuteCountQueryInternal(
                 queryParameters,
                 middleTierCallback,
                 state );
      }
    }

    public override CountResult EndCount( IAsyncResult asyncResult )
    {
      CountResult result = null;

      // Retrieve the query result only if a query was done in the first place.
      if( ( !m_disposed ) && ( !asyncResult.UseCallId( DataSourceProviderEnumerator<T>.NilCountCallId ) ) )
      {
        CountQueryResult queryResult = m_provider.EndExecuteCountQueryInternal( asyncResult );
        if( queryResult != null )
        {
          result = new CountResult( queryResult.Count );
        }
      }

      return result;
    }

    public override IAsyncResult BeginGroupCount(
      CountParameters parameters,
      AsyncCallback callback,
      object state )
    {
      GroupCountQueryParameters queryParameters;

      // The DataSourceProvider cannot handle the query.
      if( !this.CreateGroupCountRequest( parameters.StartPosition, parameters.CountChildOnly, out queryParameters ) )
      {
        AsyncResult asyncResult = new AsyncResult( DataSourceProviderEnumerator<T>.NilGroupCountCallId, callback, state );
        asyncResult.StartSync( null );

        return asyncResult;
      }
      else
      {
        AsyncCallback middleTierCallback = this.GetMiddleTierCallback( callback );

        return m_provider.BeginExecuteGroupCountQueryInternal(
                 queryParameters,
                 middleTierCallback,
                 state );
      }
    }

    public override GroupCountResult EndGroupCount( IAsyncResult asyncResult )
    {
      GroupCountResult result = null;

      // Retrieve the query result only if a query was done in the first place.
      if( ( !m_disposed ) && ( !asyncResult.UseCallId( DataSourceProviderEnumerator<T>.NilGroupCountCallId ) ) )
      {
        GroupCountQueryResult queryResult = m_provider.EndExecuteGroupCountQueryInternal( asyncResult );
        if( queryResult != null )
        {
          IEnumerable<EstimateInt64> groupCounts = ( from count in queryResult.GroupCounts
                                                     select new EstimateInt64( count ) );

          result = new GroupCountResult( groupCounts.ToList() );
        }
      }

      return result;
    }

    public override JumpMoveAndFetchAsyncResult BeginJumpMoveAndFetch(
      JumpMoveAndFetchParameters parameters,
      AsyncCallback callback,
      object state )
    {
      this.CheckMoveAndFetchParameters( parameters.ExpectedMoveCount, parameters.ExpectedFetchCount );

      AsyncCallback middleTierCallback = this.GetMiddleTierCallback( callback );
      JumpMoveAndFetchAsyncResult asyncResult = new JumpMoveAndFetchAsyncResult(
                                                  DataSourceProviderEnumerator<T>.JumpMoveAndFetchCallId,
                                                  middleTierCallback,
                                                  state,
                                                  parameters );

      if( !this.HandleOutOfBoundMove( asyncResult, parameters ) )
        if( !this.HandleOneWayAction( asyncResult, parameters ) )
          throw new DataInternalException();

      return asyncResult;
    }

    public override JumpMoveAndFetchResult<T> EndJumpMoveAndFetch( JumpMoveAndFetchAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( DataSourceProviderEnumerator<T>.JumpMoveAndFetchCallId );

      return asyncResult.GetCallResult<JumpMoveAndFetchResult<T>>();
    }

    public override IAsyncResult BeginGetStatValues(
      StatParameters parameters,
      AsyncCallback callback,
      object state )
    {
      AsyncResult asyncResult = new AsyncResult(
                                  DataSourceProviderEnumerator<T>.GetStatValueCallId,
                                  this.GetMiddleTierCallback( callback ),
                                  state );

      this.HandleStatFunction( asyncResult, parameters );

      return asyncResult;
    }

    public override StatResults EndGetStatValues( IAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( DataSourceProviderEnumerator<T>.GetStatValueCallId );

      return asyncResult.GetCallResult<StatResults>();
    }

    protected new void CheckMoveAndFetchParameters( long expectedMoveCount, int expectedFetchCount )
    {
      base.CheckMoveAndFetchParameters( expectedMoveCount, expectedFetchCount );

      // Check if the move and the fetch part are done in the same direction.
      if( Math.Abs( Math.Sign( expectedMoveCount ) - Math.Sign( expectedFetchCount ) ) > 1 )
        throw new DataInternalException();
    }

    private bool HandleOutOfBoundMove(
      JumpMoveAndFetchAsyncResult asyncResult,
      JumpMoveAndFetchParameters parameters )
    {
      SourcePosition sourcePosition = parameters.SourcePosition;

      // Make sure we are out of bound and we aren't moving in bound.
      if( ( parameters.ExpectedMoveCount <= 0 && sourcePosition.IsBeforeFirst )
        || ( parameters.ExpectedMoveCount >= 0 && sourcePosition.IsAfterLast ) )
      {
        ReferencePoint referencePoint = ( sourcePosition.IsBeforeFirst )
                                          ? ReferencePoint.BeforeFirst
                                          : ReferencePoint.AfterLast;
        JumpMoveAndFetchResult<T> result = new JumpMoveAndFetchResult<T>(
                                             asyncResult,
                                             0,
                                             null,
                                             false,
                                             sourcePosition,
                                             new RelativeOffset( 0, referencePoint ),
                                             null );
        asyncResult.StartSync( result );

        return true;
      }

      return false;
    }

    private bool HandleOneWayAction(
      JumpMoveAndFetchAsyncResult asyncResult,
      JumpMoveAndFetchParameters parameters )
    {
      // Make sure we are moving and fetching in the same direction.
      if( Math.Abs( Math.Sign( parameters.ExpectedMoveCount ) - Math.Sign( parameters.ExpectedFetchCount ) ) > 1 )
        return false;

      // We must always fetch at least 1 item in order to keep a reference point.
      // That reference point will be use as a starting position for the next
      // operation.
      JumpMoveAndFetchParameters newParameters = parameters;
      int fetchCount = parameters.ExpectedFetchCount;
      if( parameters.ExpectedFetchCount == 0 )
      {
        fetchCount = ( Math.Sign( parameters.ExpectedMoveCount ) >= 0 ) ? 1 : -1;
        newParameters = parameters.CreateWithNewFetchCount( fetchCount );
      }

      // This anonymous method starts the one way action query.
      Func<AsyncCallback, object, IAsyncResult> begin =
        ( cb, st ) => this.BeginJumpMoveAndFetchOneWay( newParameters, cb, st );

      // This anonymous method returns the result of the one way action query.
      Func<AsyncResult, IAsyncResult, DependentCallResult> end =
        ( par, iar ) =>
        {
          JumpMoveAndFetchAsyncResult jar = ( JumpMoveAndFetchAsyncResult )iar;
          JumpMoveAndFetchResult<T> result = this.EndJumpMoveAndFetchOneWay( jar );

          // We must remove the extra item we have fetched.
          if( ( result != null ) && ( parameters.ExpectedFetchCount != newParameters.ExpectedFetchCount ) )
          {
            List<T> fetchedItems = ( result.Result ) ? new List<T>( 0 ) : null;

            return new DependentCallResult(
                      new JumpMoveAndFetchResult<T>(
                        ( JumpMoveAndFetchAsyncResult )par,
                        result.ActualMoveCount,
                        fetchedItems,
                        result.Result,
                        result.EndPositionObject,
                        result.EndPositionOffset,
                        result.TotalCount ) );
          }

          return new DependentCallResult( result );
        };

      asyncResult.Start( begin, end );

      return true;
    }

    private void HandleStatFunction( AsyncResult asyncResult, StatParameters statParameters )
    {
      IEnumerator<StatFunction> statFunctionsEnum = statParameters.StatFunctions.GetEnumerator();
      List<StatResult> results = new List<StatResult>();
      StatQueryParameters queryParameters = default( StatQueryParameters );
      bool hasNext = false;

      Action moveToNextAsync = null;
      Func<AsyncCallback, object, IAsyncResult> beginCall = null;
      Func<AsyncResult, IAsyncResult, DependentCallResult> endCall = null;

      moveToNextAsync = () =>
        #region
        {
          hasNext = statFunctionsEnum.MoveNext();
          while( hasNext && !this.CreateStatRequest( statFunctionsEnum.Current, statParameters.GroupValues, out queryParameters ) )
          {
            // The StatFunction cannot be processed.
            results.Add( new StatResult( new NotSupportedStatFunctionException() ) );
            hasNext = statFunctionsEnum.MoveNext();
          }
        };
        #endregion

      beginCall = ( cb, st ) => 
        #region
          {
            return m_provider.BeginExecuteStatQueryInternal( queryParameters, cb, st );
          };
        #endregion

      endCall = ( par, iar ) =>
        #region
        {
          StatQueryResult queryResult = m_provider.EndExecuteStatQueryInternal( iar );
          // The StatFunction cannot be processed.
          StatResult statResult = ( queryResult != null ) 
                                    ? queryResult.Result
                                    : new StatResult( new NotSupportedStatFunctionException() );
          results.Add( statResult );

          moveToNextAsync();

          return ( hasNext )
            ? new DependentCallResult( null, beginCall, endCall )
            : new DependentCallResult( new StatResults( results ) );
        };
      #endregion


      moveToNextAsync();

      if( !hasNext )
      {
        asyncResult.StartSync( new StatResults( results ) );
      }
      else
      {
        asyncResult.Start(
          beginCall,
          endCall );
      }
    }

    private JumpMoveAndFetchAsyncResult BeginJumpMoveAndFetchOneWay(
      JumpMoveAndFetchParameters parameters,
      AsyncCallback callback,
      object state )
    {
      JumpMoveAndFetchAsyncResult asyncResult = new JumpMoveAndFetchAsyncResult(
                                                  DataSourceProviderEnumerator<T>.JumpMoveAndFetchOneWayCallId,
                                                  callback,
                                                  state,
                                                  parameters );

      // Convert the JumpMoveAndFetch parameters into DataQuery parameters.
      bool useNavigationFilter;
      DataQueryParameters queryParameters = this.CreateDataRequest(
                                              parameters.SourcePosition,
                                              parameters.ExpectedMoveCount,
                                              parameters.ExpectedFetchCount,
                                              out useNavigationFilter );

      // This anonymous method starts the query on the underlying data source provider.
      Func<AsyncCallback, object, IAsyncResult> begin =
        ( cb, st ) =>
        {
          IAsyncResult iar = m_provider.BeginExecuteDataQueryInternal( queryParameters, cb, st );

          // Queue the async result in case the query must be canceled.
          if( !iar.CompletedSynchronously )
          {
            lock( m_pendingRequests )
            {
              m_pendingRequests.AddFirst( iar );
            }
          }

          return iar;
        };

      // This anonymous method returns the query result.
      Func<AsyncResult, IAsyncResult, DependentCallResult> end =
        ( par, iar ) =>
        {
          // Remove the async result from the queue
          if( !iar.CompletedSynchronously )
          {
            lock( m_pendingRequests )
            {
              m_pendingRequests.Remove( asyncResult );
            }
          }

          DataQueryResult dataResult = m_provider.EndExecuteDataQueryInternal( iar );
          if( m_disposed )
            return new DependentCallResult( null );

          // The total count we have obtain might not be the total count
          // we wanted since a navigation filter might have filtered out
          // items we wanted to be included in the count.
          if( useNavigationFilter )
          {
            dataResult.TotalCount = null;
          }

          // Try to get the total items count if we can get it right away.
          if( ( dataResult.TotalCount == null )
            && ( m_provider.CanCount )
            && ( !m_provider.IsAsynchronous ) )
          {
            // This anonymous method start the count query.
            Func<AsyncCallback, object, IAsyncResult> beginCount =
              ( cb, st ) => this.BeginCount( new CountParameters(), cb, st );
            // This anonymous returns the items count.
            Func<AsyncResult, IAsyncResult, DependentCallResult> endCount =
              ( p, ar ) =>
              {
                CountResult countResult = this.EndCount( ar );
                if( ( countResult != null ) && ( !countResult.Count.IsEstimate ) )
                {
                  dataResult.TotalCount = countResult.Count.Value;
                }

                return new DependentCallResult( dataResult );
              };

            return new DependentCallResult( null, beginCount, endCount );
          }
          else
          {
            return new DependentCallResult( dataResult );
          }
        };

      asyncResult.Start( begin, end );

      return asyncResult;
    }

    private JumpMoveAndFetchResult<T> EndJumpMoveAndFetchOneWay( JumpMoveAndFetchAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( DataSourceProviderEnumerator<T>.JumpMoveAndFetchOneWayCallId );

      if( m_disposed )
        return null;

      DataQueryResult dataResult = asyncResult.GetCallResult<DataQueryResult>();
      Nullable<long> actualMoveCount = null;
      SourcePosition endPosition = asyncResult.SourcePosition;
      RelativeOffset endPositionOffset = null;
      List<T> fetchedItems = ( dataResult.Data != null )
                               ? dataResult.Data.Cast<T>().ToList()
                               : new List<T>( 0 );
      EstimateInt64 totalCount = null;

      // Find out the actual move count and the end position.
      if( fetchedItems.Count > 0 )
      {
        actualMoveCount = asyncResult.ExpectedMoveCount;
        endPosition = new ItemPosition( fetchedItems.Last() );
      }
      else if( asyncResult.ExpectedMoveCount == 0 )
      {
        actualMoveCount = 0;
      }

      bool isHeadingForward = ( Math.Sign( asyncResult.ExpectedMoveCount + asyncResult.ExpectedFetchCount ) >= 0 );

      // Find out the inline count from the last fetched item.
      if( dataResult.InlineCount.HasValue )
      {
        long inlineCount = dataResult.InlineCount.Value;
        long delta = Math.Abs( actualMoveCount.GetValueOrDefault( 0 ) ) + Math.Max( 0, fetchedItems.Count - 1 );

        if( delta > 0 )
        {
          bool exceed = false;

          // Since we are starting out of bound, our calculated inline count need an adjustment.
          SourcePosition sourcePosition = asyncResult.SourcePosition;
          exceed |= ( isHeadingForward && sourcePosition.IsBeforeFirst )
                 || ( !isHeadingForward && sourcePosition.IsAfterLast );

          // Since a SortPosition may swallow a move to get on the first item, our calculated
          // inline count need an adjustment.
          exceed |= ( asyncResult.ExpectedMoveCount != 0 )
                 && ( sourcePosition is SortPosition );

          if( exceed )
          {
            delta--;
          }
        }

        ReferencePoint referencePoint = ( isHeadingForward )
                                          ? ReferencePoint.AfterLast
                                          : ReferencePoint.BeforeFirst;
        endPositionOffset = new RelativeOffset( inlineCount - delta, referencePoint );

        // Find out the actual move count using the inline count.
        if( !actualMoveCount.HasValue )
        {
          SourcePosition sourcePosition = asyncResult.SourcePosition;

          // It takes one move to get in bound from an out of bound position.
          // A SortPosition may have swallowed a move to get on the first item, the
          // actual move count must be adjusted accordingly.
          int extraMove = 0;
          if( sourcePosition is SortPosition || sourcePosition.IsOutOfBound )
          {
            extraMove = 1;
          }

          long moveCount = inlineCount + extraMove;
          actualMoveCount = ( isHeadingForward ) ? moveCount : -moveCount;
        }
      }

      // Find out the total count.
      if( dataResult.TotalCount != null )
      {
        totalCount = dataResult.TotalCount;
      }
      // Find out the total count from the inline count.
      else if( ( dataResult.InlineCount.HasValue )
            && ( asyncResult.ExpectedMoveCount != 0 )
            && ( asyncResult.SourcePosition.IsOutOfBound ) )
      {
        totalCount = dataResult.InlineCount.Value;
      }

      // We must not include the extra item we've fetched to get the end position.
      if( asyncResult.ExpectedFetchCount == 0 )
      {
        fetchedItems.Clear();
      }

      bool successfulMove = ( actualMoveCount.HasValue && actualMoveCount.Value == asyncResult.ExpectedMoveCount );
      bool successfulFetch = ( ( fetchedItems.Count * Math.Sign( asyncResult.ExpectedFetchCount ) ) == asyncResult.ExpectedFetchCount );

      // Find out the end position that must be returned.
      if( !successfulMove || !successfulFetch )
      {
        endPosition = ( isHeadingForward )
                        ? SourcePosition.AfterLast
                        : SourcePosition.BeforeFirst;
      }

      // Prepare the fetched items collection to return.
      ICollection<T> items = fetchedItems;
      bool isAtEndOfSource = endPosition.IsOutOfBound;

      if( isAtEndOfSource )
      {
        if( fetchedItems.Count <= 0 )
        {
          items = null;
        }

        // Adjust the end position offset since we know exactly what the offset should be.
        ReferencePoint referencePoint = ( endPosition.IsBeforeFirst )
                                          ? ReferencePoint.BeforeFirst
                                          : ReferencePoint.AfterLast;
        endPositionOffset = new RelativeOffset( 0, referencePoint );
      }

      bool success = ( successfulMove )
                  && ( successfulFetch )
                  && ( !isAtEndOfSource );

      return new JumpMoveAndFetchResult<T>(
               asyncResult,
               actualMoveCount,
               items,
               success,
               endPosition,
               endPositionOffset,
               totalCount );
    }

    private bool CreateCountRequest(
      SortPosition startPosition,
      bool countChildOnly,
      out CountQueryParameters parameters )
    {
      parameters = default( CountQueryParameters );

      // The DataSourceProvider doesn't support counting.
      if( !m_provider.CanCount )
        return false;

      SortRequest sortRequest;
      JumpRequest jumpRequest;
      FilterGroupRequest filterGroupRequest;
      FilterRequest filterRequest;

      // The DataSourceProvider cannot handle the query.
      if( !this.CreateCountRequest(
            startPosition,
            countChildOnly,
            out sortRequest,
            out jumpRequest,
            out filterGroupRequest,
            out filterRequest ) )
        return false;

      parameters = new CountQueryParameters(
                     sortRequest,
                     filterRequest,
                     jumpRequest,
                     filterGroupRequest );

      return true;
    }

    private bool CreateCountRequest(
      SortPosition startPosition,
      bool countChildOnly,
      out SortRequest sortRequest,
      out JumpRequest jumpRequest,
      out FilterGroupRequest filterGroupRequest,
      out FilterRequest filterRequest )
    {
      sortRequest = null;
      jumpRequest = null;
      filterGroupRequest = null;
      filterRequest = null;

      FilterExpressionBase filterExpression = null;

      // Create some kind of positioning request.
      if( ( startPosition != null ) && ( startPosition.SortingReference.Count > 0 ) )
      {
        IList<SortDescription> sortOrder = startPosition.SortingReference;
        IList<object> sortValues = startPosition.SortValues;

        if( sortValues.Count > 0 )
        {
          bool startBefore = ( startPosition.StartPosition == StartPosition.Before );

          // Use the jump approch if supported.
          if( ( !countChildOnly ) && ( m_provider.CanJump ) )
          {
            JumpStartPosition jumpStartPosition = ( startBefore ) ? JumpStartPosition.Before : JumpStartPosition.After;
            IEnumerable<JumpValue> jumpValues = ( from value in sortValues
                                                  select new JumpValue( value ) );

            jumpRequest = new JumpRequest( jumpValues, jumpStartPosition );
          }
          // Filter the group if supported.
          else if( ( countChildOnly ) && ( m_provider.CanFilterGroup ) )
          {
            IEnumerable<FilterGroupValue> filterValues = ( from value in sortValues
                                                           select new FilterGroupValue( value ) );

            filterGroupRequest = new FilterGroupRequest( filterValues );
          }
          // Convert the jump or the group filter into a filter.
          else
          {
            sortOrder = sortOrder.Take( sortValues.Count ).ToList();

            // The DataSourceProvider cannot navigate using a filter on the selected properties.
            if( !NavigationFilterUtils.IsSupported( m_provider, sortOrder ) )
              return false;

            filterExpression = ( countChildOnly )
                                 ? NavigationFilterUtils.CreateFilterExpression( sortOrder, sortValues )
                                 : NavigationFilterUtils.CreateFilterExpression( sortOrder, sortValues, false, startBefore );
          }
        }

        sortRequest = new SortRequest( sortOrder );
      }

      if( sortRequest != null )
      {
        // The DataSourceProvider cannot sort with the current requested sort order.
        if( !m_provider.IsSortOrderSupported( sortRequest.SortDescriptions ) )
          return false;
      }

      filterRequest = this.CreateFilterRequest( filterExpression );

      return true;
    }

    private DataQueryParameters CreateDataRequest(
      SourcePosition sourcePosition,
      long moveCount,
      int fetchCount,
      out bool useNavigationFilter )
    {
      if( fetchCount == 0 )
        throw new ArgumentException( "fetchCount must be less than or greater than zero.", "fetchCount" );

      // Get the sort to apply on the data source.
      bool isHeadingForward = ( Math.Sign( moveCount + fetchCount ) >= 0 );
      IEnumerable<SortDescription> sortOrder = this.GetCurrentSortOrder( !isHeadingForward );

      SortRequest sortRequest = null;
      if( sortOrder.Any() )
      {
        sortRequest = new SortRequest( sortOrder );
      }

      if( sortRequest != null )
      {
        // The DataSourceProvider cannot sort with the current requested sort order.
        if( !m_provider.IsSortOrderSupported( sortRequest.SortDescriptions ) )
          throw new DataInternalException();
      }

      // We must reduce the move counter by 1 if the source position is currently
      // out of bound.  It takes 1 move to get in bound.
      ItemJumpRequest itemJumpRequest = null;
      JumpRequest jumpRequest = null;
      FilterExpressionBase navigationFilterExpression = null;

      if( sourcePosition.IsOutOfBound )
      {
        // A move counter of 0 while the source position is out of bound should have
        // been handled in the HandleOutOfBoundMove method.  This condition cannot
        // be fixed or handled from here.
        if( moveCount == 0 )
          throw new DataInternalException();

        moveCount += -Math.Sign( moveCount );
      }
      else if( sourcePosition is ItemPosition )
      {
        ItemPosition itemPosition = ( ItemPosition )sourcePosition;
        T startingItem = itemPosition.Item as T;

        // A jump cannot be done if no item is specified.
        if( startingItem == null )
          throw new DataInternalException();

        itemJumpRequest = new ItemJumpRequest( startingItem );
      }
      else if( sourcePosition is SortPosition )
      {
        // The move counter must be adjusted since it takes 1 move to get on the first
        // item.  A move counter of 0 is left unchanged.
        moveCount += -Math.Sign( moveCount );

        SortPosition sortPosition = ( SortPosition )sourcePosition;
        bool reverse = ( sortPosition.StartPosition == StartPosition.Before ) ^ ( isHeadingForward );

        if( m_provider.CanJump )
        {
          IEnumerable<JumpValue> sortValues = ( from value in sortPosition.SortValues
                                                select new JumpValue( value ) );
          JumpStartPosition jumpStartPosition = ( reverse )
                                                  ? JumpStartPosition.After
                                                  : JumpStartPosition.Before;
          jumpRequest = new JumpRequest( sortValues, jumpStartPosition );
        }
        // Convert the SortPosition into a Filter in order to keep only the desired items.
        else if( !m_provider.IsPrimaryKeyVolatile )
        {
          IList<SortDescription> sorting = sortOrder.Take( sortPosition.SortValues.Count ).ToList();
          bool includeStartingItem = !reverse;

          navigationFilterExpression = NavigationFilterUtils.CreateFilterExpression(
            sorting,
            sortPosition.SortValues,
            false,
            includeStartingItem );
        }
        else
        {
          // We cannot convert to SortPosition in a way that will be understood 
          // by the DataSourceProvider.
          throw new DataInternalException();
        }
      }
      else
      {
        throw new DataInternalException();
      }

      SkipRequest skipRequest = null;
      if( moveCount != 0 )
      {
        skipRequest = new SkipRequest( Math.Abs( moveCount ) );
      }

      FilterRequest filterRequest = this.CreateFilterRequest( navigationFilterExpression );
      useNavigationFilter = ( navigationFilterExpression != null );

      TakeRequest takeRequest = new TakeRequest( Math.Abs( fetchCount ) );
      return new DataQueryParameters(
               takeRequest,
               skipRequest,
               sortRequest,
               filterRequest,
               itemJumpRequest,
               jumpRequest );
    }

    private bool CreateGroupCountRequest(
      SortPosition startPosition,
      bool countChildOnly,
      out GroupCountQueryParameters parameters )
    {
      parameters = default( GroupCountQueryParameters );

      // The DataSourceProvider doesn't support group counting.
      if( !m_provider.CanGroupCount )
        return false;

      SortRequest sortRequest;
      JumpRequest jumpRequest;
      FilterGroupRequest filterGroupRequest;
      FilterRequest filterRequest;

      // The DataSourceProvider cannot handle the query.
      if( !this.CreateCountRequest(
            startPosition,
            countChildOnly,
            out sortRequest,
            out jumpRequest,
            out filterGroupRequest,
            out filterRequest ) )
        return false;

      parameters = new GroupCountQueryParameters(
                     sortRequest,
                     filterRequest,
                     jumpRequest,
                     filterGroupRequest,
                     null,
                     null );

      return true;
    }

    private bool CreateStatRequest(
      StatFunction statFunction,
      IEnumerable<object> groupValues,
      out StatQueryParameters parameters )
    {
      parameters = default( StatQueryParameters );

      if( statFunction == null )
        throw new ArgumentNullException( "statFunction" );

      // The stat function hasn't been specified or isn't supported.
      if( ( !m_provider.CanCalculateStats )
        || ( !m_provider.IsStatFunctionSupported( statFunction ) ) )
        return false;

      SortRequest sortRequest = null;
      FilterGroupRequest filterGroupRequest = null;
      FilterExpressionBase filterGroupExpression = null;
      int groupCount = ( groupValues != null ) ? groupValues.Count() : 0;

      if( groupCount > 0 )
      {
        IEnumerable<SortDescription> currentSortOrder = this.GetCurrentSortOrder( false );
        int currentSortCount = currentSortOrder.Count();
        Debug.Assert( groupCount <= currentSortCount );

        if( m_provider.CanFilterGroup )
        {
          sortRequest = new SortRequest( currentSortOrder.Take( groupCount ).ToList() );
          filterGroupRequest = new FilterGroupRequest( ( from gv in groupValues
                                                         select new FilterGroupValue( gv ) ).ToList() );
        }
        else if( m_provider.CanFilter )
        {
          IEnumerator<string> sortedPropertyNames = ( from sd in currentSortOrder
                                                      select sd.PropertyName ).GetEnumerator();
          IEnumerator<object> groupValuesEnumerator = groupValues.GetEnumerator();

          // Create a filter expression that will keep only the items of the group.
          while( groupValuesEnumerator.MoveNext() )
          {
            Debug.Assert( sortedPropertyNames.MoveNext() );

            FilterExpression currentGroupFilterExpression = new FilterExpression(
                                                              sortedPropertyNames.Current, 
                                                              FilterOperator.Equal,
                                                              groupValuesEnumerator.Current );

            if( filterGroupExpression != null )
            {
              filterGroupExpression = new AndFilterExpression(
                                        filterGroupExpression,
                                        currentGroupFilterExpression );
            }
            else
            {
              filterGroupExpression = currentGroupFilterExpression;
            }
          }

          // The filter expression to get a specific group is unsupported.
          // There is no way we can apply the StatFunction.
          if( !m_provider.CanFilterBy( filterGroupExpression ) )
            return false;
        }
        // There is no way we can apply the StatFunction on a specific group.
        else
        {
          return false;
        }
      }

      if( sortRequest != null )
      {
        // The DataSourceProvider cannot sort with the current requested sort order.
        if( !m_provider.IsSortOrderSupported( sortRequest.SortDescriptions ) )
          return false;
      }

      FilterRequest filterRequest = this.CreateFilterRequest( filterGroupExpression );
      StatRequest statRequest = new StatRequest( statFunction );

      parameters = new StatQueryParameters(
                     statRequest,
                     sortRequest,
                     filterRequest,
                     filterGroupRequest );

      return true;
    }

    private IEnumerable<SortDescription> GetCurrentSortOrder( bool reverse )
    {
      IEnumerable<SortDescription> sortOrder = m_dataHelper.CompleteSort;
      if( !reverse )
        return sortOrder;

      return ( from sort in sortOrder
               select new SortDescription(
                        sort.PropertyName,
                        ( sort.Direction == ListSortDirection.Ascending )
                          ? ListSortDirection.Descending
                          : ListSortDirection.Ascending ) );
    }


    private ListSortDirection GetSortDirection( ListSortDirection direction, bool reverse )
    {
      if( reverse )
      {
        direction = ( direction == ListSortDirection.Ascending )
                      ? ListSortDirection.Descending
                      : ListSortDirection.Ascending;
      }

      return direction;
    }

    private void OnDataSourceChanged( object sender, DataSourceChangedEventArgs e )
    {
      if( e == null )
        return;

      if( e.Action == DataSourceChangedAction.Add )
      {
        IEnumerable<T> items = e.NewItems.Cast<T>();
        Func<T, bool> predicate = this.CreateFilterPredicate();

        if( predicate != null )
        {
          items = items.Where( predicate );
        }

        foreach( T item in items )
        {
          this.RaiseDataAdd( item );
        }
      }
      else if( e.Action == DataSourceChangedAction.Remove )
      {
        IEnumerable<T> items = e.OldItems.Cast<T>();
        Func<T, bool> predicate = this.CreateFilterPredicate();

        if( predicate != null )
        {
          items = items.Where( predicate );
        }

        foreach( T item in items )
        {
          this.RaiseDataRemoved( item, null, null );
        }
      }
      else if( e.Action == DataSourceChangedAction.Replace )
      {
        Debug.Assert( e.NewItems.Count == e.OldItems.Count );

        Func<T, bool> predicate = this.CreateFilterPredicate();

        int count = e.NewItems.Count;
        for( int i = 0; i < count; i++ )
        {
          T oldItem = ( T )e.OldItems[ i ];
          T newItem = ( T )e.NewItems[ i ];

          if( object.Equals( oldItem, newItem ) )
          {
            this.RaiseDataReplaced( oldItem, newItem );
          }
          else
          {
            bool isOldItemAccepted = true;
            bool isNewItemAccepted = true;

            if( predicate != null )
            {
              isOldItemAccepted = predicate.Invoke( oldItem );
              isNewItemAccepted = predicate.Invoke( newItem );
            }

            // The item has been replaced.
            if( isOldItemAccepted && isNewItemAccepted )
            {
              this.RaiseDataReplaced( oldItem, newItem );
            }
            // The item has been removed.
            else if( isOldItemAccepted )
            {
              this.RaiseDataRemoved( oldItem, null, null );
            }
            // The item has been added.
            else if( isNewItemAccepted )
            {
              this.RaiseDataAdd( newItem );
            }
          }
        }
      }
      else if( e.Action == DataSourceChangedAction.ItemChanged )
      {
        // The event must be raised for each item since we cannot guarantee
        // the item didn't pass the general filter earlier.
        foreach( ItemChangedDescription itemChanged in e.ItemsChanged )
        {
          ItemChangedInfo itemChangedInfo;

          if( itemChanged.ChangedProperties.Count == 0 )
          {
            itemChangedInfo = new ItemChangedInfo( itemChanged.Item );
          }
          else
          {
            List<string> propertiesName = new List<string>();
            Dictionary<string, object> oldValues = new Dictionary<string, object>();

            foreach( PropertyChangedDescription propertyChanged in itemChanged.ChangedProperties )
            {
              string propertyName = propertyChanged.PropertyName;
              propertiesName.Add( propertyName );

              object oldValue = propertyChanged.OldValue;
              if( oldValue != PropertyChangedDescription.UnsetValue )
              {
                oldValues.Add( propertyName, oldValue );
              }
            }

            itemChangedInfo = ( oldValues.Count > 0 )
                                ? new ItemChangedInfo( itemChanged.Item, propertiesName )
                                : new ItemChangedInfo( itemChanged.Item, propertiesName, oldValues );
          }

          this.RaiseDataItemChanged( itemChangedInfo );
        }
      }
      else if( e.Action == DataSourceChangedAction.Reset )
      {
        this.RaiseDataReset();
      }
    }

    private AsyncCallback GetMiddleTierCallback( AsyncCallback callback )
    {
      if( callback == null )
        return null;

      return ( IAsyncResult asyncResult ) =>
        {
          if( !m_disposed )
          {
            callback.Invoke( asyncResult );
          }
        };
    }

    private Func<T, bool> CreateFilterPredicate()
    {
      if( ( m_filterHandler == null )
        || ( m_filterHandler.Filter == null && m_filterHandler.FilterExpression == null ) )
        return null;

      Func<T, bool> filterPredicate = null;
      if( m_filterHandler.Filter != null )
      {
        Predicate<object> filter = m_filterHandler.Filter;
        filterPredicate = ( T item ) => filter.Invoke( item.Data );
      }

      Func<T, bool> filterExpressionPredicate = null;
      if( m_filterHandler.FilterExpression != null )
      {
        PredicateBuilder pb = new PredicateBuilder( m_filterHandler.FilterExpression );
        Func<object, bool> newFilterExpressionPredicate = pb.ToPredicate<object>(
                                                            m_provider.ElementType,
                                                            m_provider.GetElementMemberType );

        filterExpressionPredicate = ( T item ) => newFilterExpressionPredicate.Invoke( item );
      }

      if( filterPredicate == null )
      {
        return filterExpressionPredicate;
      }
      else if( filterExpressionPredicate == null )
      {
        return filterPredicate;
      }
      // Create a predicate that is combining the two predicates.
      else
      {
        return ( T item ) => filterPredicate.Invoke( item )
                          && filterExpressionPredicate.Invoke( item );
      }
    }

    private FilterRequest CreateFilterRequest( FilterExpressionBase extraFilterExpression )
    {
      Predicate<object> predicate = null;
      FilterExpressionBase predicateExpression = null;

      // Keep the filters that are assign globally on the data source.
      if( m_filterHandler != null )
      {
        // A FilteringEnumerator will filter the data if the data source provider doesn't
        // support filtering or doesn't handle filter predicates.  Otherwise, it is the
        // data source provider's responsability to filter the data according to the 
        // predicates specified by high order enumerators.
        if( m_provider.CanFilterBy( m_filterHandler.Filter ) )
        {
          predicate = m_filterHandler.Filter;
        }

        if( m_provider.CanFilterBy( m_filterHandler.FilterExpression ) )
        {
          predicateExpression = m_filterHandler.FilterExpression;
        }
      }

      // Add the requested filter to the default query.
      if( extraFilterExpression != null )
      {
        // Combine the new filter to the global filter.
        if( predicateExpression != null )
        {
          predicateExpression = new AndFilterExpression( predicateExpression, extraFilterExpression );
        }
        else
        {
          predicateExpression = extraFilterExpression;
        }
      }

      FilterRequest filterRequest = null;
      if( ( predicate != null ) || ( predicateExpression != null ) )
      {
        filterRequest = new FilterRequest( predicate, predicateExpression );
      }

      return filterRequest;
    }

    #region IDisposable Members

    protected override void Dispose( bool disposing )
    {
      if( !m_disposed )
      {
        m_disposed = true;

        // Cancel any pending requests.
        foreach( IAsyncResult iar in m_pendingRequests )
        {
          m_provider.CancelQuery( iar );
        }
        m_pendingRequests.Clear();

        DataSourceChangedEventManager.RemoveListener( m_provider, this );

        base.Dispose( disposing );
      }
    }

    #endregion

    #region IWeakEventListener Members

    bool ISLWeakEventListener.ReceiveWeakEvent( Type managerType, object sender, EventArgs e )
    {
      if( managerType == typeof( DataSourceChangedEventManager ) )
      {
        this.OnDataSourceChanged( sender, ( DataSourceChangedEventArgs )e );
        return true;
      }

      return false;
    }

    #endregion

    #region Private Fields

    private DataSourceProvider m_provider; //null
    private DataSourceProviderHelper m_dataHelper; //null
    private FilteringHandler m_filterHandler; //null
    private LinkedList<IAsyncResult> m_pendingRequests = new LinkedList<IAsyncResult>();
    private bool m_disposed; //false

    #endregion
  }
}
